package com.fs.sys.service;

import cn.hutool.http.useragent.UserAgent;
import com.alibaba.fastjson2.JSON;
import com.fs.common.bean.LoginResp;
import com.fs.common.bean.UserContext;
import com.fs.common.bean.UserContextHolder;
import com.fs.common.bean.UserInfoResp;
import com.fs.common.exception.AppException;
import com.fs.common.utils.AuthUtils;
import com.fs.common.utils.WebUtils;
import com.fs.sys.bean.dto.ClientDTO;
import com.fs.sys.bean.dto.UserDTO;
import com.fs.sys.bean.dto.UserLoginDTO;
import com.fs.common.ErrorCodes;
import com.fs.sys.entityservice.ClientEntityService;
import com.fs.sys.entityservice.UserEntityService;
import com.fs.sys.entityservice.UserLoginEntityService;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;

import static com.fs.common.config.security.AppPasswordEncoder.LOCAL_LOGIN_PWD;

/**
 * Security登录服务
 *
 * @author LiuQi 2024/8/15-10:19
 * @version V1.0
 **/
@Slf4j
@Service
public class LoginService {
    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private HttpServletResponse response;

    @Autowired
    private ClientEntityService clientEntityService;

    @Value("${spring.security.domain:ngq.com}")
    private String domain;

    @Value("${spring.security.timeout:30}")
    private Integer timeoutInMinute;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private UserLoginEntityService userLoginEntityService;

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private UserEntityService userEntityService;

    @Autowired
    private PhoneMessageService phoneMessageService;

    /**
     * 用户登录
     *
     * @param username 用户名
     * @param password 密码
     * @return 登录结果，包括token及用户信息
     */
    public LoginResp login(String username, String password) {
        Authentication authentication = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
        Authentication resp = authenticationManager.authenticate(authentication);
        log.info("登录结果: {}", JSON.toJSONString(resp));
        SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
        securityContext.setAuthentication(resp);
        SecurityContextHolder.setContext(securityContext);

        UserContext userContext = (UserContext) resp.getPrincipal();
        String token = setCookieToken(userContext);

        UserInfoResp userInfo = new UserInfoResp();
        BeanUtils.copyProperties(userContext, userInfo);
        LoginResp result = new LoginResp();
        result.setAccessToken(token);
        result.setUserInfo(userInfo);

        // 记录用户登录日志
        UserLoginDTO userLoginDTO = new UserLoginDTO();
        userLoginDTO.setUsername(username);
        userLoginDTO.setLoginTime(LocalDateTime.now());
        UserAgent userAgent = WebUtils.getUserAgent(request);
        userLoginDTO.setBrowser(userAgent.getBrowser().getName());
        userLoginDTO.setOs(userAgent.getOs().getName());
        userLoginDTO.setIp(WebUtils.getRequestHost(request));
        userLoginEntityService.insert(userLoginDTO);

        return result;
    }

    /**
     * 重新生成token，同时设置cookie
     * @param userContext 用户信息
     * @return token
     */
    public String setCookieToken(UserContext userContext) {
        String token = AuthUtils.generateToken(userContext);
        UserContextHolder.set(userContext);

        // 设置Cookie
        Cookie cookie = new Cookie("access_token", token);
        cookie.setDomain(domain);
        cookie.setPath("/");
        cookie.setHttpOnly(true);

        // 设置cookie过期时间，此处过期时间设置长一些，通过Redis控制token的过期与延长
        cookie.setMaxAge(20 * 24 * 60 * 60);
        redisTemplate.opsForValue().set("user-" + userContext.getUserId(), "1", timeoutInMinute, TimeUnit.MINUTES);
        response.addCookie(cookie);
        return token;
    }

    /**
     * 客户端登录
     *
     * @param clientId     客户端id
     * @param clientSecret 客户端密钥
     * @return 登录结果
     */
    public String loginByClient(String clientId, String clientSecret) {
        ClientDTO client = clientEntityService.findById(clientId).orElseThrow(() -> AppException.of(ErrorCodes.BASE_CLIENT_INVALID));
        if (!client.getSecret().equals(clientSecret)) {
            throw AppException.of(ErrorCodes.BASE_CLIENT_INVALID);
        }

        if (client.getDisabled()) {
            throw AppException.of(ErrorCodes.BASE_CLIENT_DISABLED);
        }

        UserContext userContext = new UserContext();
        userContext.setUserId(client.getId());
        userContext.setUsername(client.getId());
        userContext.setNickname(client.getName());
        userContext.setIsClient(true);

        Authentication authentication = UsernamePasswordAuthenticationToken.authenticated(userContext, null, userContext.getAuthorities());
        SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
        securityContext.setAuthentication(authentication);
        SecurityContextHolder.setContext(securityContext);

        String token = AuthUtils.generateToken(userContext);
        redisTemplate.opsForValue().set("user-" + userContext.getUserId(), "1", timeoutInMinute, TimeUnit.MINUTES);
        return token;
    }

    /**
     * 本地登录
     *
     * @param userName 用户名
     * @return token
     */
    public LoginResp localLogin(String userName) {
        return this.login(userName, LOCAL_LOGIN_PWD);
    }

    /**
     * 退出登录
     */
    public void logout() {
        UserContextHolder.getUserId().ifPresent(userId -> {
            // 删除在线数据
            redisTemplate.delete("user-" + userId);

            // 清除缓存
            Cookie cookie = new Cookie("access_token", null);
            cookie.setDomain(domain);
            cookie.setPath("/");
            cookie.setHttpOnly(true);
            response.addCookie(cookie);
        });
    }

    /**
     * 手机号登录
     *
     * @param phone 手机号
     * @param code  验证码
     * @return 登录后token
     */
    public LoginResp loginByPhone(String phone, String code) {
        UserDTO user = userEntityService.findByPhone(phone).orElseThrow(AppException.supplier(ErrorCodes.BASE_USER_PHONE_NOT_EXISTS));
        phoneMessageService.verifyCode(phone, code);

        UserContext userContext = new UserContext();
        userContext.setUserId(user.getId());
        userContext.setUsername(user.getUsername());
        userContext.setNickname(user.getNickname());
        userContext.setIsClient(false);
        userContext.setTenantId(user.getCurrentTenantId());
        userContext.setIsSuperAdmin(user.getIsSuperAdmin());
        userContext.setAvatar(user.getPhoto());

        Authentication authentication = UsernamePasswordAuthenticationToken.authenticated(userContext, null, userContext.getAuthorities());
        SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
        securityContext.setAuthentication(authentication);
        SecurityContextHolder.setContext(securityContext);

        String token = AuthUtils.generateToken(userContext);
        redisTemplate.opsForValue().set("user-" + userContext.getUserId(), "1", timeoutInMinute, TimeUnit.MINUTES);
        LoginResp resp = new LoginResp();
        resp.setAccessToken(token);
        return resp;
    }
}
